//  Copyright (c) 2008  Yoong-ki Ahn & Youngjin Kim < mexif@googlegroups.com>
//  See the enclosed file COPYING for license information (MIT Licence).
//  If you did not receive this file,
//  see http://www.opensource.org/licenses/mit-license.php.

#define _MEXIFLIB_INSIDE
#include "mexiflib.h"
#include <memory.h>
#include <string.h>
#include <stdio.h>

typedef unsigned int uint;
typedef unsigned char byte;

mexif_return_t MEXIF_API mexif_exif_init( mexif_exif_t* exif, const void* data, unsigned int size )
{
	const unsigned char* pb = (const unsigned char*) data;
	memset( exif, 0, sizeof(mexif_exif_t) );

	if ( mexif_is_exif_marker_prefixed( data, size ) )
	{
		pb   += 6;
		size -= 6;
	}
	else
		return MEXIF_RET_MISSING_EXIF_HEADER;

	// initialize first IFD (IFD_0)
	{
		mexif_tiffheader_t tiff;
		mexif_ifd_core_t*   core;
		if ( !mexif_parse_tiff_header( pb, size, &tiff ) )
			return MEXIF_RET_MISSING_TIFF_HEADER;

		exif->endian = tiff.endian;
		core = exif->ifds + MEXIF_SECTION_0;

		core->section = MEXIF_SECTION_0;
		core->format  = MEXIF_FORMAT_EXIF_IFD;
		core->endian  = tiff.endian;
		core->data    = pb;
		core->offset  = tiff.offset;
		core->size    = size;
	}

	exif->core.data = pb;
	exif->core.size = size;

 	// initialize second IFD (IFD_1)
 	{
 		mexif_section_t section;
 		uint count;
 		const byte*		pc;
 		uint offset;
 		mexif_tag_t		tag;
 
 		mexif_section_select( &section, exif, MEXIF_SECTION_0 );
 		count = section.count;
 		pc    = section.ifd.data + section.ifd.offset;
 
 		offset = _GetU32( pc, 2 + count*12, section.ifd.endian );
 
 		if ( offset != 0 )
 		{
			mexif_ifd_core_t*   core = exif->ifds + MEXIF_SECTION_1;
 			memcpy( core, exif->ifds + MEXIF_SECTION_0, sizeof(mexif_ifd_core_t) );
 			core->section = MEXIF_SECTION_1;
 			core->offset  = offset;
 		}
 
 		//	get maker
 		if ( MEXIF_RET_OK == mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_Make ))
 			exif->core.maker  = mexif_which_maker( tag.value_pointer, tag.value_size );
 		if ( MEXIF_RET_OK == mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_Model ) )
 			exif->core.model  = mexif_which_model(  exif->core.maker, tag.value_pointer, tag.value_size );
 
 		mexif_section_close( &section );
 	}
 
 	// find other sections
 	{
 		mexif_section_t		section;
 		mexif_tag_t			tag;
 
 		mexif_section_select( &section, exif, MEXIF_SECTION_0 );
 
  		if ( MEXIF_RET_OK == mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_ExifIFDPointer ))
  		{
			mexif_ifd_core_t*   core = exif->ifds + MEXIF_SECTION_EXIF;
  			memcpy( core, exif->ifds + MEXIF_SECTION_0, sizeof(mexif_ifd_core_t) );
  			core->section = MEXIF_SECTION_EXIF;
  			core->offset  = mexif_tag_value_uint( &tag );
			MEXIF_ASSERT( core->offset < core->size );
  		}
  
  		if ( MEXIF_RET_OK == mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_GpsIFDPointer ))
  		{
			mexif_ifd_core_t*   core = exif->ifds + MEXIF_SECTION_GPS;
  			memcpy( core, exif->ifds + MEXIF_SECTION_0, sizeof(mexif_ifd_core_t) );
			core->section = MEXIF_SECTION_GPS;
			core->offset  = mexif_tag_value_uint( &tag );
			MEXIF_ASSERT( core->offset < core->size );
  		}
  
		if ( MEXIF_RET_OK == mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_InteropIFDPointer ))
  		{
			mexif_ifd_core_t*   core = exif->ifds + MEXIF_SECTION_INTEROP;
  			memcpy( core, exif->ifds + MEXIF_SECTION_0, sizeof(mexif_ifd_core_t) );
			core->section = MEXIF_SECTION_INTEROP;
			core->offset  = mexif_tag_value_uint( &tag );
			MEXIF_ASSERT( core->offset < core->size );
  		}

		// Must not reach here
		MEXIF_ASSERT( MEXIF_RET_OK != mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_MakerNote ) );
 
 		mexif_section_close( &section );
 
		if ( MEXIF_RET_OK != mexif_section_select( &section, exif, MEXIF_SECTION_EXIF ) )
		{
			if ( MEXIF_RET_OK == mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_MakerNote ))
			{
				mexif_ifd_core_t*   core = exif->ifds + MEXIF_SECTION_MAKERNOTE;
				MEXIF_ASSERT( tag.value_type == MEXIF_VALUE_UNDEFINED );
				memcpy( core, exif->ifds + MEXIF_SECTION_0, sizeof(mexif_ifd_core_t) );
				core->section = MEXIF_SECTION_MAKERNOTE;
				core->offset  = tag.value_pointer - section.exif.data;
				core->size    = tag.value_size;
				MEXIF_ASSERT( core->offset + core->size <= section.exif.size );
			}

			mexif_section_close( &section );
		}
 	}

 	//	fix makernote.
 	if ( exif->ifds[MEXIF_SECTION_MAKERNOTE].offset == 0 )
 	{
 		// no makernote.
 	}
	else if ( mexif_recognize_makernote( exif ) != MEXIF_RET_OK )
	{
		exif->core.makernote = MEXIF_MAKER_UNKNOWN;
	}

 	// unknown makernote. ignore it.
 	if ( exif->core.maker == MEXIF_MAKER_UNKNOWN || exif->core.makernote == MEXIF_MAKER_UNKNOWN )
 	{
 		memset( exif->ifds + MEXIF_SECTION_MAKERNOTE, 0, sizeof(mexif_ifd_core_t) );
 		exif->core.makernote   = MEXIF_MAKERNOTE_UNKNOWN;
 	}

	return MEXIF_RET_OK;
}

void MEXIF_API mexif_exif_close( mexif_exif_t* exif )
{
	memset( exif, 0, sizeof(mexif_exif_t) );
}

mexif_bool MEXIF_API mexif_recognize_makernote( mexif_exif_t* exif )
{
	mexif_ifd_core_t* ifd = exif->ifds + MEXIF_SECTION_MAKERNOTE;
	const byte* pb = ifd->data + ifd->offset;
	const byte* pe = ifd->data + ifd->size;
	if ( pb == 0 )
		return MEXIF_RET_CORRUPT_DATA;

	if ( exif->core.maker == MEXIF_MAKER_CANON )
	{
		exif->core.makernote   = MEXIF_MAKERNOTE_CANON;
		ifd->format = MEXIF_FORMAT_CANON_IFD;
		return MEXIF_RET_OK;
	}

	if ( exif->core.maker == MEXIF_MAKER_CASIO )
	{
		if ( memcmp( pb, "QVC\0\0\0", 6 ) == 0 )
		{
			exif->core.makernote   = MEXIF_MAKERNOTE_CASIO2;
			ifd->format = MEXIF_FORMAT_CASIO2_IFD;
			ifd->endian = MEXIF_BIG_ENDIAN;
			ifd->offset += 6;
		}
		else
		{
			exif->core.makernote   = MEXIF_MAKERNOTE_CASIO1;
			ifd->format = MEXIF_FORMAT_CASIO1_IFD;
			ifd->endian = MEXIF_BIG_ENDIAN;
		}
		return MEXIF_RET_OK;
	}

	if ( exif->core.maker == MEXIF_MAKER_FUJIFILM )
	{
		if ( memcmp( pb, "FUJIFILM", 8 ) == 0 )
		{
			exif->core.makernote   = MEXIF_MAKERNOTE_FUJIFILM;
			ifd->data   = pb;
			ifd->size   = pe - pb;
			ifd->offset = _GetU32( pb, 8, MEXIF_LITTLE_ENDIAN );
			MEXIF_ASSERT( ifd->offset == 12 );
			ifd->format = MEXIF_FORMAT_FUJIFILM_IFD;
			ifd->endian = MEXIF_LITTLE_ENDIAN;
			return MEXIF_RET_OK;
		}

		MEXIF_ASSERT( 0 );
	}

	if ( exif->core.maker == MEXIF_MAKER_KONICA || exif->core.maker == MEXIF_MAKER_MINOLTA )
	{
		//	unknown
		if ( *pb == 'M' && 0 == memcmp( pb, "MLY", 3 ) )
			return MEXIF_RET_ERROR;
		if ( *pb == 'M' && 0 == memcmp( pb, "MINOL", 5 ) )
			return MEXIF_RET_ERROR;
		if ( pb[0] == 'K' && pb[1] == 'C' )
			return MEXIF_RET_ERROR;
		if ( pb[0] == '+' && 0 == memcmp( pb, "+M+M+M+M", 8 ))
			return MEXIF_RET_ERROR;
		if ( *pb == 'C' && 0 == memcmp( pb, "CAMER", 5 ) )
			return MEXIF_RET_ERROR;
		if ( *pb == 'F' && 0 == memcmp( "FUJIFILM", pb, 8 ) )
			return MEXIF_RET_ERROR;
		if ( pb[0] == 0 && pb[1] == 0 )
			return MEXIF_RET_ERROR;

		//	somethings wrong.
		//	DiMage RD3000
		if ( _GetU16( pb, 0, ifd->endian ) > 1000 )
			return MEXIF_RET_ERROR;

		exif->core.makernote = MEXIF_MAKERNOTE_MINOLTA;
		ifd->format = MEXIF_FORMAT_MINOLTA_IFD;
		return MEXIF_RET_OK;
	}

	if ( exif->core.maker == MEXIF_MAKER_NIKON )
	{
		//	type 2
		if ( 0 == memcmp( pb, "Nikon\0\x01\0", 8 ) )
		{
			exif->core.makernote	= MEXIF_MAKERNOTE_NIKON2;
			ifd->offset += 8;
			ifd->format = MEXIF_FORMAT_NIKON2_IFD;
			return MEXIF_RET_OK;
		}
		else if ( 0 == memcmp( pb, "Nikon\0\x02\x10\0\0", 10 ) || 0 == memcmp( pb, "Nikon\0\x02\x00\0\0", 10 ) )
		{
			mexif_tiffheader_t tiff;
			pb += 10;
			if ( !mexif_parse_tiff_header( pb, pe-pb, &tiff ) )
				return MEXIF_RET_ERROR;

			exif->core.makernote = MEXIF_MAKERNOTE_NIKON1;
			ifd->format = MEXIF_FORMAT_NIKON1_IFD;
			ifd->data   = pb;
			ifd->offset = tiff.offset;
			ifd->endian = tiff.endian;

			return MEXIF_RET_OK;
		}
		else
		{
			exif->core.makernote = MEXIF_MAKERNOTE_NIKON1;
			ifd->format = MEXIF_FORMAT_NIKON1_IFD;
			return MEXIF_RET_OK;
		}
	}

	if ( exif->core.maker == MEXIF_MAKER_PANASONIC )
	{
		if ( memcmp( pb, "Panasonic\0\0\0", 12 ) == 0 )
		{
			exif->core.makernote   = MEXIF_MAKERNOTE_PANASONIC1;
			ifd->offset += 12;
			ifd->format = MEXIF_FORMAT_PANASONIC1_IFD;

			// Some models have a wrong endian tag
			if ( exif->core.model == MEXIF_MODEL_PANASONIC_DMC_LC5 )
			{
				if ( _GetU16( pb, 12, ifd->endian ) == 0x0b00 )
					ifd->endian = !ifd->endian;
			}
			// Are there any other models having incorrect endian?
			MEXIF_ASSERT( _GetU16( pb, 12, ifd->endian ) < 0x100 );
			return MEXIF_RET_OK;
		}

		//	not IFD
		else if ( memcmp( pb, "MKED", 4 ) == 0 )
			return MEXIF_RET_ERROR;
		else
		{
			//	LC-40 unknown type.
			return MEXIF_RET_ERROR;
		}
	}

	if ( exif->core.maker == MEXIF_MAKER_PENTAX )
	{
		//	unknown.
		if ( 0 == memcmp( pb, "\x01\0\x01\0\0\0\x04\0PENTAX", 14 ) )
			return MEXIF_RET_ERROR;
		if ( 0 == memcmp( pb, "AOC\0", 4 ) )
		{
			if ( pb[4] == ' ' || pb[4] == 0 )
			{
				//  optio 230 seems big endian.   (same as exif)
				//	optio 30 seems little endian. (same as exif)
				//  optio 330RS seems big endian. (same as exif)
				//  ...
				//	USE original tiff endian.
			}
			else if ( pb[4] == 'I' )
			{
				if ( ifd->endian != MEXIF_LITTLE_ENDIAN )
					ifd->endian = MEXIF_LITTLE_ENDIAN;
			}
			else
			{
				//	Pentax istD - original was Little endian.
				if ( ifd->endian != MEXIF_BIG_ENDIAN )
					ifd->endian = MEXIF_BIG_ENDIAN;
			}

			pb += 6;
			exif->core.makernote = MEXIF_MAKERNOTE_PENTAX1;
			ifd->format = MEXIF_FORMAT_PENTAX1_IFD;
			ifd->offset = pb - ifd->data;

			return MEXIF_RET_OK;
		}
		//	unknown
		else if ( 0 == memcmp( pb, "CAMER", 5 ) )
			return MEXIF_RET_ERROR;
		else if ( 0 == memcmp( pb, "6360", 4 ) )
			return MEXIF_RET_ERROR;
		//	pentax type 2
		else
		{
			exif->core.makernote = MEXIF_MAKERNOTE_PENTAX2;
			ifd->format = MEXIF_FORMAT_PENTAX2_IFD;
			ifd->data   = pb;
			ifd->size   = pe - pb;
			ifd->offset = 0;
			return MEXIF_RET_OK;
		}
		return MEXIF_RET_ERROR;
	}

	if ( exif->core.maker == MEXIF_MAKER_RICOH )
	{
		//	
		if ( 0 == memcmp( "Ricoh", pb, 5 ) || 0 == memcmp( "RICOH", pb, 6 ) )
		{
			if ( exif->core.model == MEXIF_MODEL_RICOH_RR120 )
				return MEXIF_RET_ERROR;

			pb += 8;
			ifd->endian = MEXIF_BIG_ENDIAN;
			exif->core.makernote = MEXIF_MAKERNOTE_RICOH;
			ifd->format = MEXIF_FORMAT_RICOH_IFD;
			ifd->offset = pb - ifd->data;
			//ifd->endian = exif->;

			return MEXIF_RET_OK;
		}

		// Determined as IDF at first, however length of the Subdir is too short
		else if ( exif->core.model == MEXIF_MODEL_RICOH_RDC7S )
			return MEXIF_RET_ERROR;

		// unknown formats.
		else if ( 0 == memcmp( "CAMER", pb, 6 ) )
			return MEXIF_RET_ERROR;
		else if ( 'R' == *pb )
			return MEXIF_RET_ERROR;
		else
			return MEXIF_RET_ERROR;
	}


	if ( exif->core.maker == MEXIF_MAKER_SANYO )
	{
		if ( 0 == memcmp( "SANYO\0\x01\0", pb, 8 ) )
		{
			pb += 8;

			exif->core.makernote = MEXIF_MAKERNOTE_SANYO;
			ifd->format = MEXIF_FORMAT_SANYO_IFD;
			ifd->offset = pb - ifd->data;
			//ifd->endian = MEXIF_BIG_ENDIAN;
			return MEXIF_RET_OK;
		}

		//	unknown formats.
		if ( 0 == memcmp( "SANYO\0\x02\x01", pb, 8 ) )
			return MEXIF_RET_ERROR;
		return MEXIF_RET_ERROR;
	}

	if ( exif->core.maker == MEXIF_MAKER_SIGMA )
	{
		if ( 0 == memcmp( "SIGMA\0\0\0\x01\0", pb, 10 ) )
		{
			pb += 10;

			exif->core.makernote = MEXIF_MAKERNOTE_SIGMA;
			ifd->format = MEXIF_FORMAT_SIGMA_IFD;
			ifd->offset = pb - ifd->data;
			return 1;
		}
		return MEXIF_RET_ERROR;
	}

	if ( exif->core.maker == MEXIF_MAKER_SONY )
	{
		if ( 0 == memcmp( "SONY CAM \0\0\0", pb, 12 ) || 0 == memcmp( "SONY DSC \0\0\0", pb, 12 ) )
		{
			pb += 12;

			exif->core.makernote = MEXIF_MAKERNOTE_SONY;
			ifd->format = MEXIF_FORMAT_SONY_IFD;
			ifd->offset = pb - ifd->data;
			ifd->endian = MEXIF_LITTLE_ENDIAN;

			// something wrong.
			// a-100, a-700
			if ( _GetU16( pb, 0, MEXIF_LITTLE_ENDIAN ) >= 1000 )
			{
				if ( _GetU16( pb, 0, MEXIF_BIG_ENDIAN ) < 1000 )
					ifd->endian = MEXIF_BIG_ENDIAN;
				else
					//	unknown.
					return MEXIF_RET_ERROR;
			}
			return MEXIF_RET_OK;
		}

		//	unknown format.s
		if ( 0 == memcmp( "FUJIFILM", pb, 8 ) )
			return MEXIF_RET_ERROR;

		if ( 0 == memcmp( "OLYMP\0\x01", pb, 8 ) )
			return MEXIF_RET_ERROR;

		if ( 0 == memcmp( "SONY PI", pb, 8 ) )
			return MEXIF_RET_ERROR;

		// It seems that IFD format starts after 4 bytes, but cannot find out the number of tags.
		if ( 0 == memcmp( "\0\0\0\0", pb, 4 ) )
			return MEXIF_RET_ERROR;

		if ( 0 == memcmp( "PREMI", pb, 6 ) )
			return MEXIF_RET_ERROR;

		return MEXIF_RET_ERROR;
	}

	return MEXIF_RET_ERROR;
}

mexif_return_t MEXIF_API mexif_section_select( mexif_section_t* section, mexif_exif_t* exif, mexif_section_type_t type ) 
{
	memset( section, 0, sizeof(*section) );

	//	empty. unknown ifd.
	if ( exif->ifds[type].section != type )
		return MEXIF_RET_ERROR;

	section->exif = exif->core;
	section->ifd  = exif->ifds[type];

	if ( _mexif_is_ifd( section->ifd.format ) )
	{
		section->count = _mexif_ifd_count_tags( &section->ifd );
	}

	return MEXIF_RET_OK;
}

void MEXIF_API mexif_section_close( mexif_section_t* section ) 
{
	memset( section, 0, sizeof(*section) );
}

mexif_uint32 MEXIF_API mexif_count_tags( mexif_section_t* section ) 
{
	return section->count;
}

mexif_return_t MEXIF_API mexif_search_tag( mexif_section_t* section, mexif_tag_t* tag, mexif_uint32 tagid ) 
{
	mexif_uint32 i=0;
	mexif_uint32 n = mexif_count_tags( section );

	for ( ; i<n; ++i )
	{
		if ( MEXIF_RET_OK == mexif_select_tag( section, tag, i ) && tag->tag_id == tagid )
			return MEXIF_RET_OK;
	}

	memset( tag, 0, sizeof(*tag) );
	return MEXIF_RET_ERROR;
}

mexif_return_t MEXIF_API mexif_select_tag( mexif_section_t* section, mexif_tag_t* tag, mexif_uint32 index ) 
{
	const byte* pb;

	if ( section->count <= index )
		return MEXIF_RET_ERROR;
	if ( section->ifd.data == 0 )
		return MEXIF_RET_ERROR;

	pb  = section->ifd.data + section->ifd.offset;
	memcpy( &tag->exif, &section->exif, sizeof(mexif_exif_core_t) );
	memcpy( &tag->ifd,  &section->ifd,  sizeof(mexif_ifd_core_t) );

	tag->value_format = MEXIF_FORMAT_UNKNOWN;

	if ( _mexif_is_ifd( section->ifd.format ) )
	{
		tag->tag_db_entry = 0;

		pb += 2 + index * 12;
		tag->tag_pointer  = pb;
		tag->tag_id       = _GetU16( pb, 0, tag->ifd.endian );

		{
			mexif_tagdb_meta_t db;
			mexif_tagdb_init( &db, section->ifd.format );
			if ( db.func )
				tag->tag_db_entry = db.func( tag->tag_id );

			//	sony a-700 uses minolta IFD
			if ( section->ifd.format == MEXIF_FORMAT_SONY_IFD && tag->exif.model == MEXIF_MODEL_SONY_A700 )
			{
				if ( tag->tag_db_entry == 0 )
				{
					mexif_tagdb_init( &db, MEXIF_FORMAT_MINOLTA_IFD );
					if ( db.func )
						tag->tag_db_entry = db.func( tag->tag_id );
					// MinoltaCameraSettings5D -> MinoltaCameraSettingsA100
					if ( tag->tag_id == MEXIF_TAGID_MINOLTA_MinoltaCameraSettings5D )
					{
						++tag->tag_db_entry;
						MEXIF_ASSERT( tag->tag_db_entry->id == MEXIF_TAGID_MINOLTA_MinoltaCameraSettings5D );
					}
				}
			}
		}

		tag->value_type   = _GetU16( pb, 2, tag->ifd.endian );
		tag->value_count  = _GetU32( pb, 4, tag->ifd.endian );

		switch( tag->value_type )
		{
		case MEXIF_VALUE_UINT16 :
		case MEXIF_VALUE_SINT16 :
			tag->value_size  = 2 * tag->value_count;
			break;
		case MEXIF_VALUE_UINT32 :
		case MEXIF_VALUE_SINT32 :
			tag->value_size  = 4 * tag->value_count;
			break;
		case MEXIF_VALUE_SRATIONAL :
		case MEXIF_VALUE_RATIONAL :
			tag->value_size  = 8 * tag->value_count;
			break;
		case MEXIF_VALUE_SINT8 :
		case MEXIF_VALUE_ASCII :
		case MEXIF_VALUE_BYTE :
		case MEXIF_VALUE_UNDEFINED :
			tag->value_size  = tag->value_count;
			break;
		default :
			tag->value_size  = tag->value_count;
			//assert( 0 );
			return MEXIF_RET_ERROR;
		}

		if ( tag->value_size <= 4 )
		{
			tag->value_pointer = pb + 8;
			tag->value_offset  = ( pb - section->ifd.data ) + 8;
		}
		else
		{
			tag->value_offset  = _GetU32( pb, 8, tag->ifd.endian );
			tag->value_pointer = section->ifd.data + tag->value_offset;

			//	PENTAX type 2 
			if ( tag->ifd.format == MEXIF_FORMAT_PENTAX2_IFD )
			{
				tag->value_pointer = pb + tag->value_offset;
			}
		}

		/// recognize subsection format.
		tag->value_format = _mexif_get_subsection_format( section, tag );

		// data end should be valid
		if ( tag->value_pointer + tag->value_size <= tag->exif.data + tag->exif.size )
			return MEXIF_RET_OK;
		//assert( tag->value_pointer + tag->value_size <= tag->exif.data + tag->exif.size );
		else
			return MEXIF_RET_ERROR;
		return MEXIF_RET_OK;
	}
	else if ( _mexif_is_u8_array( section->ifd.format ) )
	{
		tag->tag_db_entry = 0;

		pb += index * 2;
		tag->tag_pointer  = 0;
		tag->tag_id       = index;

		{
			mexif_tagdb_meta_t db;
			mexif_tagdb_init( &db, section->ifd.format );
			if ( db.func )
				tag->tag_db_entry = db.func( tag->tag_id );
		}

		tag->value_type    = MEXIF_VALUE_BYTE;
		tag->value_count   = 1;
		tag->value_size    = 1;
		tag->value_pointer = pb;
		tag->value_offset  = index;
		//tag->value_format = _mexif_get_subsection_format( section, tag );
		return MEXIF_RET_OK;
	}
	else if ( _mexif_is_u16_array( section->ifd.format ) )
	{
		tag->tag_db_entry = 0;

		pb += index * 2;
		tag->tag_pointer  = 0;
		tag->tag_id       = index;

		{
			mexif_tagdb_meta_t db;
			mexif_tagdb_init( &db, section->ifd.format );
			if ( db.func )
				tag->tag_db_entry = db.func( tag->tag_id );
		}

		tag->value_type    = MEXIF_VALUE_UINT16;
		tag->value_count   = 1;
		tag->value_size    = 2;
		tag->value_pointer = pb;
		tag->value_offset  = index * 2;
		//tag->value_format = _mexif_get_subsection_format( section, tag );

		//	Exceptions
		if ( section->ifd.format == MEXIF_FORMAT_MINOLTA_CameraSettings7D )
		{
			if ( index == MEXIF_TAGID_MINOLTA_CameraSettings7D_ExposureCompensation )
				tag->value_type    = MEXIF_VALUE_SINT16;
		}
		else if ( section->ifd.format == MEXIF_FORMAT_MINOLTA_CameraSettings5D )
		{
			if ( index == MEXIF_TAGID_MINOLTA_CameraSettings5D_ColorTemperature )
				tag->value_type    = MEXIF_VALUE_SINT16;
			else if ( index == MEXIF_TAGID_MINOLTA_CameraSettings5D_ColorTemperature2 )
				tag->value_type    = MEXIF_VALUE_SINT16;
		}
		//if ( section->ifd.format == MEXIF_FORMAT_CANON_FileInfo )
		//{
		//	if ( index == 1 )
		//	{
		//		tag->value_type		= MEXIF_VALUE_UINT32;
		//		tag->value_size		= 4;
		//	}
		//}
		return MEXIF_RET_OK;
	}
	else if ( _mexif_is_u32_array( section->ifd.format ) )
	{
		tag->tag_db_entry = 0;

		pb += index * 4;
		tag->tag_pointer  = 0;
		tag->tag_id       = index;

		{
			mexif_tagdb_meta_t db;
			mexif_tagdb_init( &db, section->ifd.format );
			if ( db.func )
				tag->tag_db_entry = db.func( tag->tag_id );
		}

		tag->value_type    = MEXIF_VALUE_UINT32;
		tag->value_count   = 1;
		tag->value_size    = 4;
		tag->value_pointer = pb;
		tag->value_offset  = index * 4;
		//tag->value_format = _mexif_get_subsection_format( section, tag );
		return MEXIF_RET_OK;
	}
	else
	{
		MEXIF_ASSERT( 0 );
	}

	return MEXIF_RET_ERROR;
}


mexif_return_t MEXIF_API mexif_subsection_load( mexif_section_t* section, mexif_tag_t* tag )
{
	mexif_format_t format = tag->value_format;
	memset( section, 0, sizeof(*section) );
	memcpy( &section->exif, &tag->exif, sizeof(mexif_exif_core_t) );

	if ( _mexif_is_u32_array( format ) )
	{
		section->ifd.format = format;
		section->ifd.endian = tag->ifd.endian;
		section->ifd.data   = tag->value_pointer;
		section->ifd.offset = 0;
		section->ifd.size   = tag->value_size;
		section->count      = tag->value_size / 4;
	}
	else if ( _mexif_is_u16_array( format ) )
	{
		section->ifd.format = format;
		section->ifd.endian = tag->ifd.endian;
		section->ifd.data   = tag->value_pointer;
		section->ifd.offset = 0;
		section->ifd.size   = tag->value_size;
		section->count      = tag->value_size / 2;
	}
	else if ( _mexif_is_u8_array( format ) )
	{
		section->ifd.format = format;
		section->ifd.endian = tag->ifd.endian;
		section->ifd.data   = tag->value_pointer;
		section->ifd.offset = 0;
		section->ifd.size   = tag->value_size;
		section->count      = tag->value_size;
	}
	else if ( _mexif_is_ifd( format ) )
	{
		section->ifd.format = format;
		section->ifd.endian = tag->ifd.endian;

		//	set data starting pointer
		if ( format == MEXIF_FORMAT_NIKON1_PreviewImage || format == MEXIF_FORMAT_NIKON1_AFInfo )
		{
			section->ifd.data   = tag->ifd.data;
			section->ifd.offset = mexif_tag_value_uint( tag );
			section->ifd.size   = tag->ifd.size;

			// some model has wrong offset.
			if ( tag->exif.model == MEXIF_MODEL_NIKON_COOLPIX2100 || tag->exif.model == MEXIF_MODEL_NIKON_COOLPIX3100 )
				section->ifd.offset -= 10;
		}
		else
		{
			MEXIF_ASSERT( 0 );
		}

		section->count      = _GetU16( section->ifd.data, section->ifd.offset, section->ifd.endian );

	}
	else
	{
		MEXIF_ASSERT( 0 );
	}

	return MEXIF_RET_OK;
}


mexif_uint32 MEXIF_API mexif_tag_value_uint( mexif_tag_t* tag ) 
{
	return mexif_tag_value_uint_index( tag, 0 );
}

mexif_int32 MEXIF_API mexif_tag_value_int( mexif_tag_t* tag ) 
{
	return mexif_tag_value_int_index( tag, 0 );
}

mexif_uint32 MEXIF_API mexif_tag_value_uint_index( mexif_tag_t* tag, unsigned int index )
{
	switch( tag->value_type )
	{
	case MEXIF_VALUE_UINT16 :
		return _GetU16( tag->value_pointer, 2*index, tag->ifd.endian );

	case MEXIF_VALUE_UINT32 :
		return _GetU32( tag->value_pointer, 4*index, tag->ifd.endian );

	case MEXIF_VALUE_SINT8 :
	case MEXIF_VALUE_SINT16 :
	case MEXIF_VALUE_SINT32 :
	case MEXIF_VALUE_SRATIONAL :
	case MEXIF_VALUE_RATIONAL :
	default :
		MEXIF_ASSERT( 0 );
		return 0;

	case MEXIF_VALUE_BYTE :
		return (mexif_uint32) tag->value_pointer[index];

	case MEXIF_VALUE_UNDEFINED :
		return (mexif_uint32) tag->value_pointer[index];
	}
}

mexif_int32  MEXIF_API mexif_tag_value_int_index( mexif_tag_t* tag, unsigned int index )
{
	switch( tag->value_type )
	{
	case MEXIF_VALUE_UINT16 :
		return _GetU16( tag->value_pointer, 2*index, tag->ifd.endian ) & 0xffff;

	case MEXIF_VALUE_SINT32 :
		return (int) _GetU32( tag->value_pointer, 4*index, tag->ifd.endian );

	case MEXIF_VALUE_SINT8 :
		return (signed char) tag->value_pointer[index];
	case MEXIF_VALUE_SINT16 :
		return (short) _GetU16( tag->value_pointer, 2*index, tag->ifd.endian );

	case MEXIF_VALUE_UINT32 :
	case MEXIF_VALUE_SRATIONAL :
	case MEXIF_VALUE_RATIONAL :
	default :
		MEXIF_ASSERT( 0 );
		return 0;

	case MEXIF_VALUE_BYTE :
		return tag->value_pointer[index];

	case MEXIF_VALUE_UNDEFINED :
		return tag->value_pointer[index];
	}
}

double		 MEXIF_API mexif_tag_value_double( mexif_tag_t* tag )
{
	return mexif_tag_value_double_index( tag, 0 );
}

double		 MEXIF_API mexif_tag_value_double_index( mexif_tag_t* tag, unsigned int index )
{
	switch( tag->value_type )
	{
	case MEXIF_VALUE_UINT16 :
	case MEXIF_VALUE_SINT32 :
	case MEXIF_VALUE_UINT32 :
	case MEXIF_VALUE_BYTE :
	case MEXIF_VALUE_UNDEFINED :
	default :
		MEXIF_ASSERT( 0 );
		return 0;

	case MEXIF_VALUE_SRATIONAL :
		{
			int a = _GetU32( tag->value_pointer, 0 + index*8, tag->ifd.endian );
			int b = _GetU32( tag->value_pointer, 4 + index*8, tag->ifd.endian );
			return (a) / (double) b;
		}

	case MEXIF_VALUE_RATIONAL :
		{
			uint a = _GetU32( tag->value_pointer, 0 + index*8, tag->ifd.endian );
			uint b = _GetU32( tag->value_pointer, 4 + index*8, tag->ifd.endian );
			return (a) / (double) b;
		}
	}
}


mexif_pcstr MEXIF_API mexif_tag_name( mexif_tag_t* tag ) 
{
	if ( tag->tag_db_entry )
		return tag->tag_db_entry->name;
	return 0;
}

mexif_uint32  __stdcall mexif_atoui( const unsigned char* pb )
{
	mexif_uint32 ui = 0;
	for ( ; *pb; ++pb )
	{
		switch( *pb )
		{
		case '0' :
		case '1' :
		case '2' :
		case '3' :
		case '4' :
		case '5' :
		case '6' :
		case '7' :
		case '8' :
		case '9' :
			ui = ui * 10 + *pb - '0';
			continue;
		default :
			return ui;
		}
	}

	return ui;
}

const unsigned char* __stdcall mexif_find_non_digit( const unsigned char* pb )
{
	for ( ; *pb; ++pb )
	{
		switch( *pb )
		{
		case '0' :
		case '1' :
		case '2' :
		case '3' :
		case '4' :
		case '5' :
		case '6' :
		case '7' :
		case '8' :
		case '9' :
			continue;
		default :
			return pb;
		}
	}
	return pb;
}

mexif_return_t MEXIF_API mexif_tag_value_time( mexif_tag_t* tag, mexif_time_t* t ) 
{
	//	check if tag is a time type.
	if ( tag->value_type == MEXIF_VALUE_ASCII && tag->value_count >= 20 )
	{
		const unsigned char* p = tag->value_pointer;
		memset( t, 0, sizeof(mexif_time_t) );

		// We should know how many bytes to skip
		t->year = mexif_atoui( p );
		p = mexif_find_non_digit( p );
		if ( *p == 0 )
			return MEXIF_RET_ERROR;

		++p;
		t->month = mexif_atoui( p );
		p = mexif_find_non_digit( p );
		if ( *p == 0 )
			return MEXIF_RET_ERROR;

		++p;
		t->date = mexif_atoui( p );
		p = mexif_find_non_digit( p );
		if ( *p == 0 )
			return MEXIF_RET_ERROR;

		++p;
		t->hour = mexif_atoui( p );
		p = mexif_find_non_digit( p );
		if ( *p == 0 )
			return MEXIF_RET_ERROR;

		++p;
		t->minute = mexif_atoui( p );
		p = mexif_find_non_digit( p );
		if ( *p == 0 )
			return MEXIF_RET_ERROR;

		++p;
		t->second = mexif_atoui( p );
		return MEXIF_RET_OK;
	}
	else
		return MEXIF_RET_ERROR;
}

//////////////////////////////////////////////////////////////////////////
mexif_bool __stdcall _mexif_is_ifd( mexif_format_t fmt ) 
{
	return MEXIF_GET_BASE_FORMAT( fmt ) == MEXIF_BASE_FORMAT_IFD;
}

mexif_bool __stdcall _mexif_is_u8_array( mexif_format_t fmt )
{
	return MEXIF_GET_BASE_FORMAT( fmt ) == MEXIF_BASE_FORMAT_U8ARRAY;
}

mexif_bool __stdcall _mexif_is_u16_array( mexif_format_t fmt )
{
	return MEXIF_GET_BASE_FORMAT( fmt ) == MEXIF_BASE_FORMAT_U16ARRAY;
}

mexif_bool __stdcall _mexif_is_u32_array( mexif_format_t fmt )
{
	return MEXIF_GET_BASE_FORMAT( fmt ) == MEXIF_BASE_FORMAT_U32ARRAY;
}


mexif_uint32 __stdcall _mexif_ifd_count_tags( mexif_ifd_core_t* ifd ) 
{
	const unsigned char* pb = ifd->data + ifd->offset;
	return _GetU16( pb, 0, ifd->endian );
}

mexif_format_t	__stdcall _mexif_get_subsection_format( mexif_section_t* section, mexif_tag_t* tag )
{
	mexif_format_t		cur = section->ifd.format;
	mexif_model_t		mod = section->exif.model;
	mexif_makernote_t	mak = section->exif.makernote;

	if ( cur == MEXIF_FORMAT_CANON_IFD )
	{
		switch( tag->tag_id )
		{
#define TAGID_FORMAT( x ) case MEXIF_TAGID_ ## x : return MEXIF_FORMAT_ ## x
		TAGID_FORMAT( CANON_CameraSettings );
		TAGID_FORMAT( CANON_FocalLength	   );
		TAGID_FORMAT( CANON_ShotInfo	   );	
		TAGID_FORMAT( CANON_Panorama	   );	
		TAGID_FORMAT( CANON_AFInfo		   );
		TAGID_FORMAT( CANON_MyColors	   );	
		TAGID_FORMAT( CANON_FaceDetect1	   );
		TAGID_FORMAT( CANON_FaceDetect2	   );
		TAGID_FORMAT( CANON_AFInfo2		   );
		TAGID_FORMAT( CANON_FileInfo	   );	
		TAGID_FORMAT( CANON_ProcessingInfo );
		TAGID_FORMAT( CANON_SensorInfo	   );
		TAGID_FORMAT( CANON_ColorInfo	   );
		}
	}
	else if ( cur == MEXIF_FORMAT_NIKON1_IFD )
	{
		switch( tag->tag_id )
		{
		TAGID_FORMAT( NIKON1_PreviewImage );
		TAGID_FORMAT( NIKON1_AFInfo );
		}
	}
	else if ( cur == MEXIF_FORMAT_PENTAX1_IFD )
	{
		switch( tag->tag_id )
		{
		TAGID_FORMAT( PENTAX1_ShakeReductionInfo	);
		TAGID_FORMAT( PENTAX1_ShotInfo				);
		}
	}
	else if ( cur == MEXIF_FORMAT_SONY_IFD )
	{
		//	sony a-700 uses minolta format.
		if ( mod == MEXIF_MODEL_SONY_A700 && tag->tag_id == MEXIF_TAGID_MINOLTA_MinoltaCameraSettings5D )
			return MEXIF_FORMAT_MINOLTA_CameraSettingsA100;
	}
	else if ( cur == MEXIF_FORMAT_MINOLTA_IFD )
	{
		switch( tag->tag_id )
		{
		case MEXIF_TAGID_MINOLTA_MinoltaCameraSettingsOld	:	return MEXIF_FORMAT_MINOLTA_CameraSettings;
		case MEXIF_TAGID_MINOLTA_MinoltaCameraSettings		:	return MEXIF_FORMAT_MINOLTA_CameraSettings;
		case MEXIF_TAGID_MINOLTA_MinoltaCameraSettings7D	:
			if ( tag->exif.model == MEXIF_MODEL_MINOLTA_7D )	return MEXIF_FORMAT_MINOLTA_CameraSettings7D;
			else
				break;
		case MEXIF_TAGID_MINOLTA_MinoltaCameraSettings5D :
			if ( tag->exif.model == MEXIF_MODEL_MINOLTA_5D )	return MEXIF_FORMAT_MINOLTA_CameraSettings5D;
			else
				break;
		}
	}

	return MEXIF_FORMAT_UNKNOWN;
}


mexif_rotate_t	MEXIF_API mexif_get_rotation( mexif_exif_t* exif )
{
	mexif_section_t	section;
	mexif_section_t	subsec;
	mexif_tag_t		tag;
	mexif_tag_t		subtag;
	mexif_rotate_t  rot = MEXIF_ROTATE_None;

	if ( MEXIF_RET_OK != mexif_section_select( &section, exif, MEXIF_SECTION_MAKERNOTE ))
		return MEXIF_ROTATE_None;

	//	canon.
	if ( exif->core.makernote == MEXIF_MAKERNOTE_CANON )
	{
		if ( MEXIF_RET_OK == mexif_search_tag( &section, &tag, MEXIF_TAGID_CANON_ShotInfo ) )
		{
			if ( MEXIF_RET_OK == mexif_subsection_load( &subsec, &tag ) )
			{
				if ( MEXIF_RET_OK == mexif_search_tag( &subsec, &subtag, MEXIF_TAGID_CANON_ShotInfo_AutoRotate ) )
				{
					short val = (short) mexif_tag_value_uint( &subtag );
					if ( val > 0 && val < 4 )
						rot = (mexif_rotate_t) val;
				}
			}
		}
	}

	mexif_section_close( &section );
	return rot;
}

mexif_return_t  MEXIF_API mexif_get_thumbnail( mexif_exif_t* exif, mexif_thumbnail_t* thumb )
{
	mexif_section_t	section;
	mexif_tag_t		tag;
	mexif_return_t  ret;
	mexif_uint32 offset = 0;
	mexif_uint32 length = 0;

	ret = mexif_section_select( &section, exif, MEXIF_SECTION_1 );
	if ( MEXIF_RET_OK != ret )
		return ret;


	if ( MEXIF_RET_OK == mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_JPEGInterchangeFormat ) )
		offset = mexif_tag_value_uint( &tag );

	if ( MEXIF_RET_OK == mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_JPEGInterchangeFormatLength ) )
		length = mexif_tag_value_uint( &tag );

	if ( length > 0 && offset > 0 && (offset+length) <= section.ifd.size )
	{
		thumb->data = section.ifd.data + offset;
		thumb->size = length;
		ret = MEXIF_RET_OK;
	}
	else
		ret = MEXIF_RET_CORRUPT_DATA;

	mexif_section_close( &section );
	return ret;
}

mexif_bool MEXIF_API mexif_has_gps_section( mexif_exif_t* exif )
{
	return ( exif->ifds[MEXIF_SECTION_GPS].section == MEXIF_SECTION_GPS );
}

mexif_return_t	MEXIF_API mexif_get_pictured_time( mexif_exif_t* exif, mexif_time_t* t )
{
	mexif_section_t	section;
	mexif_tag_t		tag;
	mexif_return_t  ret;

	ret = mexif_section_select( &section, exif, MEXIF_SECTION_EXIF );
	if ( MEXIF_RET_OK != ret )
		return ret;

	ret = mexif_search_tag( &section, &tag, MEXIF_TAGID_EXIF_DateTimeOriginal );
	if ( MEXIF_RET_OK == ret )
		ret = mexif_tag_value_time( &tag, t );

	mexif_section_close( &section );
	return ret;
}